package gis.web.demo;

import gis.algorithm.Extent;
import gis.algorithm.GeoPoint;
import gis.algorithm.GeoUtils;
import gis.algorithm.cluster.ClusterArg;
import gis.algorithm.cluster.ClusterCalculator;
import gis.algorithm.cluster.ClusterPoint;
import gis.algorithm.cluster.RpCluster;
import gis.algorithm.cluster.RqCluster;
import gis.algtest.BizCell;
import gis.algtest.BizService;
import gis.core.utils.Serializer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;



/**
 *
 */
@Controller("Service_Cluster")
@RequestMapping({"/service/cluster"})
public class Cluster {
    private static Logger logger = LoggerFactory.getLogger(Cluster.class);

    /**
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(method = RequestMethod.POST)
    public void doPost(HttpServletRequest request, HttpServletResponse response) {
        RqCluster rq;
        try {
            String json = request.getParameter("obj");
            rq = RqCluster.fromJson(json);
        } catch (Exception e) {
            outputError(response, e);
            return;
        }

        // 获取基础数据
        List<ClusterArg<BizCell>> clusterArgs = getClusterArgs(rq);

        if ((rq.getLevel() >= 5 && clusterArgs.size() < 400)
                || clusterArgs.size() < 200) {
            // 无需汇聚
            List<RpCluster> rps = new ArrayList<RpCluster>();
            for (ClusterArg<BizCell> clusterArg : clusterArgs) {
                RpCluster rp = new RpCluster();
                rp.setX(clusterArg.getX());
                rp.setY(clusterArg.getY());
                rp.setCount(1);
                rps.add(rp);
            }
            rps = merge(rps); // 同一基站下的小区进行合并
            output(response, rps, false);
            return;
        } else {
            // 进行汇聚
            ClusterCalculator c = ClusterCalculator.getInstance();
            List<ClusterPoint<BizCell>> points = c.calc(clusterArgs, rq);
            List<RpCluster> rps = convert(points);
            rps = merge(rps); // 同一基站下的小区进行合并
            output(response, rps, true);
            return;
        }
    }

    /**
     * 获取汇聚算法基础数据
     *
     * @param rq
     * @return
     */
    private List<ClusterArg<BizCell>> getClusterArgs(RqCluster rq) {
        BizCell[] cells = BizService.getInstance().getCells();
        Extent extent = new Extent(rq.getxMin(), rq.getxMax(), rq.getyMin(),
                rq.getyMax());

        // 根据经纬度范围筛选小区
        int wkid = rq.getWkid();
        List<ClusterArg<BizCell>> clusterArgs = new ArrayList<ClusterArg<BizCell>>();
        for (int i = 0; i < cells.length; i++) {
            double x = cells[i].getLongitude();
            double y = cells[i].getLatitude();

            // 坐标系转换
            if (wkid == 4326) {
                // 基础数据为4326坐标系
            } else if (wkid == 102100) {
                // 基础数据转102100坐标系
                GeoPoint point = GeoUtils.lonLatToMercator(x, y);
                x = point.getX();
                y = point.getY();
            } else {
                throw new NotImplementedException(String.valueOf(wkid));
            }

            if (extent.contain(x, y)) {
                ClusterArg<BizCell> arg = new ClusterArg<BizCell>();
                arg.setX(x);
                arg.setY(y);
                arg.setObj(cells[i]);
                clusterArgs.add(arg);
            }
        }
        return clusterArgs;
    }

    /**
     * 对象转换
     *
     * @param points
     * @return
     */
    private List<RpCluster> convert(List<ClusterPoint<BizCell>> points) {
        if (points == null) {
            return null;
        }
        List<RpCluster> rps = new ArrayList<RpCluster>();
        for (ClusterPoint<BizCell> point : points) {
            RpCluster rp = new RpCluster();
            rp.setX(point.getX());
            rp.setY(point.getY());
            rp.setCount(point.getCount());
            rps.add(rp);
        }
        return rps;
    }

    /**
     * 相同经纬的数据进行合并
     *
     * @param rps
     * @return
     */
    private List<RpCluster> merge(List<RpCluster> rps) {
        if (rps == null) {
            return null;
        }
        Map<String, RpCluster> map = new HashMap<String, RpCluster>();
        for (RpCluster rp : rps) {
            String key = rp.getX() + ":" + rp.getY();
            if (map.containsKey(key)) {
                RpCluster rc = map.get(key);
                int count = rc.getCount() + rp.getCount();
                rc.setCount(count);
            } else {
                map.put(key, rp);
            }
        }
        return new ArrayList<RpCluster>(map.values());
    }

    /**
     * 输出数据
     *
     * @param response
     * @param rps
     * @param clustered 是否进行了聚合算法
     */
    private void output(HttpServletResponse response, List<RpCluster> rps,
                        boolean clustered) {
        try {
            String json = Serializer.serializeJson(rps);
            response.setStatus(200);
            String str = clustered ? "1" : "0";
            response.getWriter().write(str + json);
        } catch (Exception e) {
            outputError(response, e);
        }
    }

    /**
     * 输出错误
     *
     * @param response
     * @param ex
     */
    private void outputError(HttpServletResponse response, Exception ex) {
        response.setStatus(500);
        logger.debug(ex.getMessage(), ex);
        try {
            response.getWriter().write(ex.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
